package com.pucong.gitee.database.backup.business.service.util.fileService;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.pucong.gitee.database.backup.business.service.util.fileService.pool.SftpClientWapper;
import com.pucong.gitee.database.backup.business.service.util.fileService.pool.SftpConnectionManager;
import com.pucong.gitee.database.backup.common.entity.service.ServiceFile;
import com.pucong.gitee.database.backup.common.entity.service.vo.ServiceFileTreeVo;
import com.pucong.gitee.database.backup.configuration.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

@Slf4j
public class FileOperationServiceSftpImpl extends AbstractFileOperationService {

    private SftpClientWapper sftpClientWapper = null;

    private ChannelSftp channelSftp = null;

    // 禁止删除的目录，防止错删
    private String[] CANT_DELETE_PATH = new String[]{"/", "/user", "/opt", "/data", "/home", "/root", "/var", "/user", "/sys", "/boot", "/etc", "/proc"};

    public FileOperationServiceSftpImpl(String sessionId, ServiceFile serviceFile) {
        super(serviceFile);
        sftpClientWapper = SftpConnectionManager.getConnection(sessionId, serviceFile.getHost(), serviceFile.getPort(), serviceFile.getUsername(), serviceFile.getPassword());
        channelSftp = sftpClientWapper.getSFtpChannel();
    }

    @Override
    public List<ServiceFileTreeVo> getFileServerCatalogue(String path) {
        try {
            final Vector files = channelSftp.ls(path);
            List<ServiceFileTreeVo> fileInfoList = new ArrayList<>(files.size());
            for (int i = 0; i < files.size(); i++) {
                ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) files.get(i);
                ServiceFileTreeVo serviceFileTreeVo = new ServiceFileTreeVo();
                SftpATTRS attrs = entry.getAttrs();
                String filename = entry.getFilename();
                serviceFileTreeVo.setFileTrue(!attrs.isDir());
                serviceFileTreeVo.setBank(attrs.isBlk());
                serviceFileTreeVo.setPermissions(attrs.getPermissionsString());
                serviceFileTreeVo.setLeafTrue(true);
                if (".".equals(filename)) {
                    continue;
                }
                if (attrs.isDir()) {
                    serviceFileTreeVo.setLeafTrue(false);
                }else{
                    if (filename.lastIndexOf(".") >= 0) {
                        String suffixName = filename.substring(filename.lastIndexOf(".") + 1);
                        suffixName = suffixName.toLowerCase();
                        serviceFileTreeVo.setFileSuffix(suffixName);
                    }
                }
                serviceFileTreeVo.setLink(attrs.isLink());
                serviceFileTreeVo.setFileName(filename);
                if ("/".equals(path)) {
                    serviceFileTreeVo.setFilePath("/" + filename);
                } else if ("..".equals(filename)) {
                    serviceFileTreeVo.setFilePath(getLastPath(path));
                } else {
                    serviceFileTreeVo.setFilePath(path + "/" + filename);
                }
                serviceFileTreeVo.setFileSize(attrs.getSize());
                serviceFileTreeVo.setLastUpdateTime(new Date(attrs.getMTime() * 1000L));
                fileInfoList.add(serviceFileTreeVo);
            }
            return fileInfoList;
        } catch (SftpException e) {
            log.error("获取文件目录失败", e);
            throw new BusinessException(e.getMessage());
        }
    }

    /**
     * 获取上级路径
     *
     * @param path
     * @return
     */
    private String getLastPath(String path) {
        String[] split = path.split("/");
        if (split.length == 2) {
            return "/";
        }
        split = Arrays.copyOf(split, split.length - 1);
        return String.join("/", split);
    }

    @Override
    public boolean uploadFile(InputStream inputStream, String outputPath, String fileName) {
        try {
            // 创建目录
            mkdirs(outputPath);
            channelSftp.cd(outputPath);
            channelSftp.put(inputStream, fileName);
        } catch (Exception e) {
            log.error("上传文件失败", e);
            throw new BusinessException("上传文件失败");
        }
        return true;
    }

    @Override
    public boolean addCatalogue(String filePath, String addName) {
        String dir = filePath + "/" + addName;
        if (dir == null && dir.length() == 0) {
            return false;
        }
        String md = dir.replaceAll("//", "/");
        md = dir.replaceAll("\\\\", "/");
        if (md.indexOf("/") != 0 || md.length() == 1) {
            return false;
        }
        return mkdirs(md);
    }

    /**
     * 循环创建文件夹.
     *
     * @param dir 路径
     * @return 是否创建成功
     * @throws SftpException 异常
     */
    private boolean mkdirs(String dir) {
        String[] dirArr = dir.split("/");
        String base = "";
        for (String d : dirArr) {
            base += "/" + d;
            if (dirExist(base + "/")) {
                continue;
            } else {
                try {
                    channelSftp.mkdir(base + "/");
                } catch (SftpException e) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 判断文件夹是否存在.
     *
     * @param dir 文件夹路径， /xxx/xxx/
     * @return 是否存在
     */
    private boolean dirExist(String dir) {
        try {
            Vector<?> vector = channelSftp.ls(dir);
            if (null == vector) {
                return false;
            } else {
                return true;
            }
        } catch (SftpException e) {
            return false;
        }
    }

    @Override
    public boolean deleteFile(String filePath) {

        int idx = filePath.lastIndexOf("/");
        String fileName = filePath.substring(idx + 1, filePath.length());
        String directory = filePath.substring(0, idx);
        try {
            channelSftp.cd(directory);
            channelSftp.rm(fileName);
        } catch (Exception e) {
            log.error("删除文件失败", e);
            throw new BusinessException("删除文件失败");
        }
        return true;
    }

    @Override
    public boolean deleteCatalogue(String filePath) {
        if (StringUtils.isBlank(filePath) || Arrays.asList(CANT_DELETE_PATH).contains(filePath)) {
            throw new BusinessException("错误的删除目录，或者该目录不能删除");
        }
        try {
            // 递归删除目录和文件
            List<ServiceFileTreeVo> filePathByRecursive = getFilePathByRecursive(filePath);
            filePathByRecursive.sort((o1, o2) -> {
                String[] split1 = o1.getFilePath().split("/");
                String[] split2 = o2.getFilePath().split("/");
                return split2.length - split1.length;
            });
            for (ServiceFileTreeVo serviceFileTreeVo : filePathByRecursive) {
                if(serviceFileTreeVo.getFileTrue()){
                    deleteFile(serviceFileTreeVo.getFilePath());
                }else{
                    channelSftp.rmdir(serviceFileTreeVo.getFilePath());
                }
            }
            // 删除本身目录
            channelSftp.rmdir(filePath);
        } catch (Exception e) {
            log.error("删除目录失败", e);
            throw new BusinessException("删除目录失败");
        }
        return true;
    }

    @Override
    public void downloadFile(OutputStream out, String filePath) {
        int idx = filePath.lastIndexOf("/");
        String fileName = filePath.substring(idx + 1, filePath.length());
        String directory = filePath.substring(0, idx);
        try {
            channelSftp.cd(directory);
            channelSftp.get(fileName, out);
        } catch (Exception e) {
            log.error("下载文件失败", e);
            throw new BusinessException("下载文件失败");
        }
    }

    @Override
    public void lookFile(OutputStream out, String filePath) {
        //进行读写操作
        try {
            downloadFile(out, filePath);
        } catch (Exception e) {
            log.error("文件流io异常", e);
        } finally {
            //善后工作，关闭各种流
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                log.error("文件流io异常", e);
            }
        }
    }

    @Override
    public void close() {
        sftpClientWapper.close();
    }

    @Override
    public Boolean connectTest() {
        if (channelSftp != null) {
            close();
            return true;
        }
        return false;
    }

    @Override
    public boolean rename(String filePath, String oldName, String newName) {
        String fromPath = filePath  + "/" + oldName;
        String toPath = filePath  + "/" + newName;
        if (StringUtils.isBlank(fromPath) || Arrays.asList(CANT_DELETE_PATH).contains(fromPath)) {
            throw new BusinessException("错误的重命名目录，或者该目录不能重命名");
        }
        try {
            channelSftp.rename(fromPath, toPath);
        } catch (Exception e) {
            log.error("重命名文件失败", e);
            throw new BusinessException("重命名文件失败");
        }
        return true;
    }

    /**
     * 递归获取子目录和文件
     *
     * @param path
     * @return
     */
    private List<ServiceFileTreeVo> getFilePathByRecursive(String path) {
        List<ServiceFileTreeVo> result = new ArrayList<>();
        List<ServiceFileTreeVo> fileServerCatalogue = getFileServerCatalogue(path);
        for (ServiceFileTreeVo serviceFileTreeVo : fileServerCatalogue) {
            if (".".equals(serviceFileTreeVo.getFileName()) || "..".equals(serviceFileTreeVo.getFileName())) {
                continue;
            }
            result.add(serviceFileTreeVo);
            if (!serviceFileTreeVo.getFileTrue()) {
                List<ServiceFileTreeVo> fileServerCatalogue2 = getFilePathByRecursive(serviceFileTreeVo.getFilePath());
                result.addAll(fileServerCatalogue2);
            }

        }
        return result;
    }
}
